#include <stdio.h>
#include <string.h>
#include <math.h>
#include "gmp.h"
#include <pthread.h>
#include <sys/time.h>

// Declara global de todas as variaveis como float da biblioteca GMP
mpf_t a0, a, b0, b, t0, t, p0, p, pi, temp1, temp2, temp3;

void *calcula_a_t(void *thread_id)
{
    // calcula a:
    mpf_add(temp1, a0, b0);
    mpf_div_ui(a, temp1, 2.0);

    // calcula t:
    mpf_sub(temp1, a0, a);
    mpf_mul(temp2, temp1, temp1);
    mpf_mul(temp1, p0, temp2);
    mpf_sub(t, t0, temp1);

    return NULL;
}

void *calcula_b(void *thread_id)
{
    // calcula b:
    mpf_mul(temp3, a0, b0);
    mpf_sqrt(b, temp3);

    return NULL;
}

void *calcula_p(void *thread_id)
{
    // calcula p:
    mpf_mul_ui(p, p0, 2.0);

    return NULL;
}

int main()
{
    // Declara variaveis para fazer o calculo do speedup do programa
    struct timeval antes, depois;

    // Pega a hora atual do sistema (desde o Epoch), para saber quando o programa comecou a executar
    gettimeofday(&antes, 0);

    // Declara a variavel para manipular os arquivos que guardarao o valor calculado de PI a cada iteracao
    FILE *arq;

    // Declara strings para definir o nome dos arquivos que guardarao os valores de PI a cada iteracao
    char nome_arq[15];
    char nro_arq[3];

    // Declaracao das threads
    pthread_t thread1, thread2, thread3;

    // Determina a precisao padrao das variaveis do tipo mpf_t
    mpf_set_default_prec(40000000);

    // Inicia todas as variaveis com o valor 0
    // Variaveis utilizadas no Algoritmo de Gauss-Legendre
    mpf_init(a0);
    mpf_init(b0);
    mpf_init(t0);
    mpf_init(p0);
    mpf_init(a);
    mpf_init(b);
    mpf_init(t);
    mpf_init(p);
    // Variavel que guarda o resultado final encontrado
    mpf_init(pi);
    // Variaveis utilizadas para auxiliar nas operacoes aritimeticas com as funcoes da biblioteca GMP
    mpf_init(temp1);
    mpf_init(temp2);
    mpf_init(temp3);

    // Coloca os valores iniciais das variaveis que serao utilizadas no algoritmo
    // a = a0 = 1.0
    mpf_set_d(a0, 1.0);
    mpf_set_d(a, 1.0);
    // b = b0 = 1.0/sqrt(2)
    mpf_sqrt_ui(temp1, 2.0);
    mpf_ui_div(temp2, 1.0, temp1);
    mpf_set(b, temp2);
    mpf_set(b0, b);
    // t = t0 = 1.0/4.0
    mpf_set_d(t, 0.25);
    mpf_set(t0, t);
    // p = p0 = 1.0
    mpf_set_d(p, 1.0);
    mpf_set(p0, p);

    // Variavel responsavel por controlar quantidade de iteracoes realizadas pelo algoritmo
    int i;

    // O algoritmo duplica a sua precisao a cada iteracao. Queremos 10 milhoes de casas decimais de precisao.
    // Portanto, precisamos de 24 iteracoes, pois pow(2,24) = 16777216 > 10000000.
    for(i = 1; i <= 24; i++)
    {
        // Cria as threads que serao utilizadas para realizar os calculos, a cada iteracao do algoritmo, das variaveis a, b, t e p.
        pthread_create(&thread1, NULL, calcula_a_t, (void *)1);
        pthread_create(&thread2, NULL, calcula_b, (void *)2);
        pthread_create(&thread3, NULL, calcula_p, (void *)3);

        // Espera cada thread terminar para poder atualizar os valores das variáveis iniciais.
        pthread_join(thread1, NULL);
        pthread_join(thread2, NULL);
        pthread_join(thread3, NULL);

        // Atualiza o valor de a0, b0, t0, p0
        mpf_set(a0, a);
        mpf_set(b0, b);
        mpf_set(t0, t);
        mpf_set(p0, p);

        // Calcula o valor de PI
        mpf_add(temp1, a, b);
        mpf_mul(temp2, temp1, temp1);
        mpf_mul_ui(temp1, t, 4.0);
        mpf_div(pi, temp2, temp1);

        // Manipula as strings para criar o nome do arquivo de cada uma das iteracoes
        sprintf(nro_arq, "%d", i);
        strcpy(nome_arq, "GaussParalelo");
        strcat(nome_arq, nro_arq);
        strcat(nome_arq, ".txt");

        // Abre o arquivo que vai guardar o resultado de PI na iteracao atual
        arq = fopen(nome_arq, "w");
        if(arq == NULL)
        {
            printf("Problema ao abrir o arquivo");
            return 1;
        }

        // Imprime o valor calculado de PI no arquivo
        gmp_fprintf(arq, "%.10000000Ff", pi);

        // Fecha o arquivo desta iteracao
        fclose(arq);
    }

    // Limpa o espaco de memoria das variaveis utilizadas.
    mpf_clear(a0);
    mpf_clear(b0);
    mpf_clear(t0);
    mpf_clear(p0);
    mpf_clear(a);
    mpf_clear(b);
    mpf_clear(t);
    mpf_clear(p);
    mpf_clear(pi);
    mpf_clear(temp1);
    mpf_clear(temp2);
    mpf_clear(temp3);

    // Pega a hora atual do sistema (desde o Epoch), para saber quando o programa terminou de executar
    gettimeofday(&depois, 0);

    // Declara a variavel que guardara o valor do speedup do programa
    float speedup;

    // Faz a subtracao entre o horario que o programa terminou e o horario que o programa comecou, para saber quanto tempo ele levou para ser executado
    speedup = (depois.tv_sec + depois.tv_usec/1000000.0) - (antes.tv_sec + antes.tv_usec/1000000.0);
    
    // Abre o arquivo que vai guardar os speedups
    arq = fopen("Speedups.txt", "a+");
    if(arq == NULL){
	printf("Problema ao abrir o arquivo");
    	return 1;
    }

    // Imprime o speedup do programa no arquivo Speedups.txt
    fprintf(arq,"Gauss Paralelo : %.10f segundos.\n", speedup);
    
    // Fecha o arquivo de speedup
    fclose(arq);

    return 0;
}
